#include "particlesystem.h"

ParticleSystem::ParticleSystem(QObject *parent) : QObject(parent)
{
    srand((unsigned int)time(0)); //Seed the random number generator
    TimerID= startTimer(15);
    ElapsedTime= 0;
    Steps= 0.0;

    /** Estableciendo valores por default de las propiedades**/
    name= "default_particle_system";
    TextureID= 0.0;
    Amount= 512;

    // Velocidad
    StepSize= 0.007;
    StepVar= 0.0004;

    // Origen
    SourcePosition.x= SourcePosition.y= SourcePosition.z= 0.0;
    SourceVar.x= 0.1; SourceVar.y= 0.1; SourceVar.z= 0.1;

    // Color
    StartColor.x= 0.95; StartColor.y= 1.0; StartColor.z= 0.93; StartColor.w= 0.0;
    StartColorVar.x= StartColorVar.y= StartColorVar.z= 0.05;
    EndColor.x= 0.95; EndColor.y= 0.0; EndColor.z= 0.00; EndColor.w= 0.0;
    EndColorVar.x= EndColorVar.y= EndColorVar.z= 0.05;

    // Direccion
    Direction.x= 0.0; Direction.y= 1.0; Direction.z= 0.0;
    DirectionVar.x= DirectionVar.y= DirectionVar.z= 0.0;

    // Tamaño
    Size.x= Size.y= Size.z= 0.009;
    SizeVar.x= SizeVar.y= SizeVar.z= 0.0005;

    // Tiempo de Vida
    TimeToLive= 150;
    TimeToLiveVar= 25;

    // Blending
    SrcBlending= GL_ZERO;
    DstBlending= GL_ZERO;
    AlphaFunction= GL_ALWAYS;
    AlphaReference= 0.0;

    // Para una especie de DepthSort
    ZOffset= 0.0;

    GenerateColorStep();
}

float ParticleSystem::randomFloat()
{
    return (float)rand() / ((float)RAND_MAX + 1);
}

int ParticleSystem::randomInt(int max)
{
    if (max!= 0)
        return rand() % max + 1;
    else
        return 0;
}

float ParticleSystem::randomSign()
{
    if (randomFloat()<=0.5)
        return -1.0;
    else
        return 1.0;
}

void ParticleSystem::timerEvent(QTimerEvent* e)
{
    Steps+= StepSize;
    ElapsedTime++;
}

// Este metodo se encargara de construir/reconstruir el sistema de particulas con los parametros disponibles
void ParticleSystem::BuildSystem()
{
    // Cualquier cosa existente, adios...
    Particles.clear();

    // Para hacer una especie de depth sort
    ZUnit= SourceVar.z / Amount;

    for (int i=0; i<Amount; i++)
    {
        particle p;

        // Colores
        p.color[0]= StartColorVar.x * randomFloat() * randomSign();
        p.color[1]= StartColorVar.y * randomFloat() * randomSign();
        p.color[2]= StartColorVar.z * randomFloat() * randomSign();
        p.color[4]= 0.0;

        // Posicion de Origen
        p.sourcepos[0]= SourceVar.x * randomFloat() * randomSign();
        p.sourcepos[1]= SourceVar.y * randomFloat() * randomSign();
        p.sourcepos[2]= ZOffset;//SourceVar.z * randomFloat() * randomSign();
        ZOffset+= ZUnit;

        // Posicion
        p.pos[0]= p.sourcepos[0] + SourcePosition.x;
        p.pos[1]= p.sourcepos[1] + SourcePosition.y;
        p.pos[2]= p.sourcepos[2] + SourcePosition.z;

        // Direccion
        p.dir[0]= DirectionVar.x * randomFloat() * randomSign();
        p.dir[1]= DirectionVar.y * randomFloat() * randomSign();
        p.dir[2]= DirectionVar.z * randomFloat() * randomSign();

        // Tamaño
        p.size[0]= SizeVar.x * randomFloat();
        p.size[1]= SizeVar.y * randomFloat();
        p.size[2]= SizeVar.z * randomFloat();

        // Time to live
        p.timetolive= TimeToLive + int(randomSign()) * randomInt(TimeToLiveVar);
        p.timeleft= p.timetolive;

        // Speed
        p.speed= StepVar * randomFloat() * randomSign();

        Particles.append(p);
    }

    GenerateColorStep();
}

// Este metodo va a darle a la tarjeta de video toda la informacion de las particulas
void ParticleSystem::Render(QMatrix4x4 projection, QMatrix4x4 view)
{
    SetBillBoardView(projection, view);

    DrawParticles();

    Steps= 0.0;
    ElapsedTime= 0;
}

void ParticleSystem::SetBillBoardView(QMatrix4x4 projection, QMatrix4x4 view)
{
    // Cargamos nuestra matrix de proyeccion
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    float* M1= new float [16];
    qreal* T1= projection.data();
    for (int i=0; i<16; i++)
    {
        M1[i]= float(T1[i]);
    }
    glLoadMatrixf(M1);
    //glLoadMatrixf(projection.data());

    // Y ahora la matriz de modelo
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    float* M2= new float [16];
    qreal* T2= view.data();

    for (int i=0; i<16; i++)
    {
        M2[i]= float(T2[i]);
    }
    glLoadMatrixf((float*)M2);

    //glLoadMatrixf(view.data());

    /*
    glPushMatrix();
    QVector3D aux= QVector3D(view.transposed() * view.column(3));
    QVector3D cam= QVector3D(-1.2, -3.5, -7.2);
    QVector3D objPosWC= cam + aux;

    QVector3D lookAt, objToCamProj, objToCam, upAux;

    float modelview[16], angleCosine;

    objToCamProj.setX(cam.x() - objPosWC.x());
    objToCamProj.setY(0.0);
    objToCamProj.setZ(cam.z() - objPosWC.z());

    lookAt.setX(0.0);
    lookAt.setY(0.0);
    lookAt.setZ(0.0);

    objToCamProj.normalize();

    upAux= QVector3D::crossProduct(lookAt, objToCamProj);
    angleCosine= QVector3D::dotProduct(lookAt, objToCamProj);

    if ((angleCosine < 0.99990) && (angleCosine > -0.9999))
          glRotatef(acos(angleCosine)*180/3.14, upAux.x(), upAux.y(), upAux.z());

    objToCam.setX(cam.x() - objPosWC.x());
    objToCam.setY(cam.y() - objPosWC.y());
    objToCam.setZ(cam.z() - objPosWC.z());

    objToCam.normalize();

    angleCosine= QVector3D::dotProduct(objToCamProj, objToCam);

    if ((angleCosine < 0.99990) && (angleCosine > -0.9999))
        if (objToCam.x() < 0)
            glRotatef(acos(angleCosine)*180/3.14,1,0,0);
        else
            glRotatef(acos(angleCosine)*180/3.14,-1,0,0);*/
}

void ParticleSystem::DrawParticles()
{
    glEnable(GL_BLEND);
    glEnable(GL_ALPHA_TEST);

    glBlendFunc(SrcBlending, DstBlending);
    glAlphaFunc(AlphaFunction, AlphaReference);
    //glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);



    if (HASTEXTURE)
    {
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, TextureID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }

    glBegin(GL_TRIANGLES);
        for (int i= 0; i<Particles.size(); i++)
        {
            particle *p= &Particles[i];

            p->timeleft-= ElapsedTime;

            if (p->timeleft< 0)
            {
                p->dir[0]= DirectionVar.x * randomFloat() * randomSign();
                p->dir[1]= DirectionVar.y * randomFloat() * randomSign();
                p->dir[2]= DirectionVar.z * randomFloat() * randomSign();

                p->sourcepos[0]= SourceVar.x * randomFloat() * randomSign();
                p->sourcepos[1]= SourceVar.y * randomFloat() * randomSign();

                if (ZOffset< SourceVar.z) // Aun queda profundidad para dibujar!
                {
                    p->sourcepos[2]= ZOffset;
                    ZOffset+= ZUnit;
                }
                else
                {
                    // Empezamos a dibujar desde profundidad 0
                    p->sourcepos[2]= ZOffset= 0.0;
                }

                p->pos[0]= p->sourcepos[0] + SourcePosition.x;
                p->pos[1]= p->sourcepos[1] + SourcePosition.y;
                p->pos[2]= p->sourcepos[2] + SourcePosition.z;

                p->timetolive= TimeToLive + int(randomSign()) * randomInt(TimeToLiveVar);

                p->speed= StepVar * randomFloat() * randomSign();

                p->timeleft= p->timetolive;
            }
            else
            {
                p->pos[0]+= (p->speed + Steps) * (p->dir[0] + Direction.x);
                p->pos[1]+= (p->speed + Steps) * (p->dir[1] + Direction.y);
                p->pos[2]+= (p->speed + Steps) * (p->dir[2] + Direction.z);
            }

            // Color interpolado entre el inicio y el final
            float t= float(p->timetolive - p->timeleft);

            glColor4f(p->color[0] + StartColor.x + (ColorStep.x * t),
                      p->color[1] + StartColor.y + (ColorStep.y * t),
                      p->color[2] + StartColor.z + (ColorStep.z * t),
                      p->color[3] + StartColor.w + (ColorStep.w * t));
                      //float(p->timeleft/2) / float(p->timetolive));

          /* qDebug("This: %f %f %f %f", p->color[0] + StartColor.x + (ColorStep.x * t),
                   p->color[1] + StartColor.y + (ColorStep.y * t),
                   p->color[2] + StartColor.z + (ColorStep.z * t),
                   p->color[3] + StartColor.w + (ColorStep.w * t));*/

            if (HASTEXTURE) glTexCoord2f(0, 0);
            glVertex3f(p->pos[0] - (p->size[0] + Size.x), p->pos[1] - (p->size[1] + Size.y), p->pos[2]);

            if (HASTEXTURE) glTexCoord2f(1, 0);
            glVertex3f(p->pos[0] + (p->size[0] + Size.x), p->pos[1] - (p->size[1] + Size.y), p->pos[2]);

            if (HASTEXTURE) glTexCoord2f(1, 1);
            glVertex3f(p->pos[0] + (p->size[0] + Size.x), p->pos[1] + (p->size[1] + Size.y), p->pos[2]);

            if (HASTEXTURE) glTexCoord2f(1, 1);
            glVertex3f(p->pos[0] + (p->size[0] + Size.x), p->pos[1] + (p->size[1] + Size.y), p->pos[2]);

            if (HASTEXTURE) glTexCoord2f(0, 1);
            glVertex3f(p->pos[0] - (p->size[0] + Size.x), p->pos[1] + (p->size[1] + Size.y), p->pos[2]);

            if (HASTEXTURE) glTexCoord2f(0, 0);
            glVertex3f(p->pos[0] - (p->size[0] + Size.x), p->pos[1] - (p->size[1] + Size.y), p->pos[2]);
        }
    glEnd();

    glDisable(GL_BLEND);
    glDisable(GL_ALPHA_TEST);
}

void ParticleSystem::GenerateColorStep()
{
    // Ahora calculemos el step de color
    ColorStep.x= (EndColor.x - StartColor.x) / float(TimeToLive);
    ColorStep.y= (EndColor.y - StartColor.y) / float(TimeToLive);
    ColorStep.z= (EndColor.z - StartColor.z) / float(TimeToLive);
    ColorStep.w= (EndColor.w - StartColor.w) / float(TimeToLive);
}

/** Setters de Propiedades **/
void ParticleSystem::setName(QString input)
{
    name= input;
}

void ParticleSystem::setTextureID(GLuint input)
{
    TextureID= input;
    HASTEXTURE= true;
}

void ParticleSystem::setTextureURL(QString url)
{
    TextureURL= url;
    HASTEXTURE= true;
}

void ParticleSystem::setSourcePosition(float x, float y, float z)
{
    SourcePosition.x= x;
    SourcePosition.y= y;
    SourcePosition.z= z;
}

void ParticleSystem::setSourceVar(float x, float y, float z)
{
    SourceVar.x= x;
    SourceVar.y= y;
    SourceVar.z= z;

    // Como se pudo haber modificado la varianza en Z, modificamos ZUnit
    ZUnit= SourceVar.z / Amount;
}

// Ajusta la velocidad poniendole el intervalo de actualizacion al timer
void ParticleSystem::setStepSize(float input)
{
    StepSize= input;
}

void ParticleSystem::setStepVar(float x)
{
    StepVar= x;
}

void ParticleSystem::setStartColor(float r, float g, float b, float a)
{
    StartColor.x= r;
    StartColor.y= g;
    StartColor.z= b;
    StartColor.w= a;

    GenerateColorStep();
}

void ParticleSystem::setStartColorVar(float x, float y, float z)
{
    StartColorVar.x= x;
    StartColorVar.y= y;
    StartColorVar.z= z;
}

void ParticleSystem::setEndColor(float r, float g, float b, float a)
{
    EndColor.x= r;
    EndColor.y= g;
    EndColor.z= b;
    EndColor.w= a;

    GenerateColorStep();
}

void ParticleSystem::setEndColorVar(float x, float y, float z)
{
    EndColorVar.x= x;
    EndColorVar.y= y;
    EndColorVar.z= z;
}

void ParticleSystem::setDir(float x, float y, float z)
{
    Direction.x= x;
    Direction.y= y;
    Direction.z= z;
}

void ParticleSystem::setDirVar(float x, float y, float z)
{
    DirectionVar.x= x;
    DirectionVar.y= y;
    DirectionVar.z= z;
}

void ParticleSystem::setSize(float x, float y, float z)
{
    Size.x= x;
    Size.y= y;
    Size.z= z;
}

void ParticleSystem::setSizeVar(float x, float y, float z)
{
    SizeVar.x= x;
    SizeVar.y= y;
    SizeVar.z= z;
}

void ParticleSystem::setTimeToLive(int input)
{
    TimeToLive= input;

    GenerateColorStep();
}

void ParticleSystem::setTimeToLiveVar(int input)
{
    TimeToLiveVar= input;
}

void ParticleSystem::setAmount(int input)
{
    Amount= input;
}

void ParticleSystem::setSrcBlending(short int input)
{
    // Traduccion a constantes de OpenGL
    switch(input)
    {
        case 0: SrcBlending= GL_ZERO; break;
        case 1: SrcBlending= GL_ONE; break;
        case 2: SrcBlending= GL_DST_COLOR; break;
        case 3: SrcBlending= GL_SRC_COLOR; break;
        case 4: SrcBlending= GL_ONE_MINUS_DST_COLOR; break;
        case 5: SrcBlending= GL_ONE_MINUS_SRC_COLOR; break;
        case 6: SrcBlending= GL_SRC_ALPHA; break;
        case 7: SrcBlending= GL_ONE_MINUS_SRC_ALPHA; break;

        default: break;
    }
}

void ParticleSystem::setDstBlending(short int input)
{
    // Traduccion a constantes de OpenGL
    switch(input)
    {
        case 0: DstBlending= GL_ZERO; break;
        case 1: DstBlending= GL_ONE; break;
        case 2: DstBlending= GL_DST_COLOR; break;
        case 3: DstBlending= GL_SRC_COLOR; break;
        case 4: DstBlending= GL_ONE_MINUS_DST_COLOR; break;
        case 5: DstBlending= GL_ONE_MINUS_SRC_COLOR; break;
        case 6: DstBlending= GL_SRC_ALPHA; break;
        case 7: DstBlending= GL_ONE_MINUS_SRC_ALPHA; break;

        default: break;
    }
}

void ParticleSystem::setBlending(short int src, short int dst, short funct, float ref)
{
    setSrcBlending(src);
    setDstBlending(dst);
    setAlphaFunction(funct);
    setAlphaReference(ref);
}

void ParticleSystem::setAlphaFunction(short input)
{
    // Traduccion a constantes de OpenGL
    switch(input)
    {
        case 0: AlphaFunction= GL_NEVER; break;
        case 1: AlphaFunction= GL_LESS; break;
        case 2: AlphaFunction= GL_EQUAL; break;
        case 3: AlphaFunction= GL_LEQUAL; break;
        case 4: AlphaFunction= GL_GREATER; break;
        case 5: AlphaFunction= GL_NOTEQUAL; break;
        case 6: AlphaFunction= GL_GEQUAL; break;
        case 7: AlphaFunction= GL_ALWAYS; break;

        default: break;
    }
}

void ParticleSystem::setAlphaReference(float input)
{
    if (input > 1.0) AlphaReference= 1.0;
    else if (input < 0.0) AlphaReference= 0.0;
    else AlphaReference= input;

}

/** Getters de Propiedades **/

QString ParticleSystem::getName()
{
    return name;
}

bool ParticleSystem::HasTexture()
{
    return HASTEXTURE;
}

QString ParticleSystem::getTextureURL()
{
    return TextureURL;
}

GLuint ParticleSystem::getTextureID()
{
    return TextureID;
}

float* ParticleSystem::getSourcePosition()
{
    float* output= new float [3];

    output[0]= SourcePosition.x;
    output[1]= SourcePosition.y;
    output[2]= SourcePosition.z;

    return output;
}

float* ParticleSystem::getSourceVar()
{
    float* output= new float [3];

    output[0]= SourceVar.x;
    output[1]= SourceVar.y;
    output[2]= SourceVar.z;

    return output;
}

// Retorna la frecuencia de activacion del timer
float ParticleSystem::getStepSize()
{
    return StepSize;
}

float ParticleSystem::getStepVar()
{
    return StepVar;
}

float* ParticleSystem::getStartColor()
{
    float* output= new float [4];

    output[0]= StartColor.x;
    output[1]= StartColor.y;
    output[2]= StartColor.z;
    output[3]= StartColor.w;

    return output;
}

float* ParticleSystem::getStartColorVar()
{
    float* output= new float [3];

    output[0]= StartColorVar.x;
    output[1]= StartColorVar.y;
    output[2]= StartColorVar.z;

    return output;
}

float* ParticleSystem::getEndColor()
{
    float* output= new float [4];

    output[0]= EndColor.x;
    output[1]= EndColor.y;
    output[2]= EndColor.z;
    output[3]= EndColor.w;

    return output;
}

float* ParticleSystem::getEndColorVar()
{
    float* output= new float [3];

    output[0]= EndColorVar.x;
    output[1]= EndColorVar.y;
    output[2]= EndColorVar.z;

    return output;
}

float* ParticleSystem::getDir()
{
    float* output= new float [3];

    output[0]= Direction.x;
    output[1]= Direction.y;
    output[2]= Direction.z;

    return output;
}

float* ParticleSystem::getDirVar()
{
    float* output= new float [3];

    output[0]= DirectionVar.x;
    output[1]= DirectionVar.y;
    output[2]= DirectionVar.z;

    return output;
}

float* ParticleSystem::getSize()
{
    float* output= new float [3];

    output[0]= Size.x;
    output[1]= Size.y;
    output[2]= Size.z;

    return output;
}

float* ParticleSystem::getSizeVar()
{
    float* output= new float [3];

    output[0]= SizeVar.x;
    output[1]= SizeVar.y;
    output[2]= SizeVar.z;

    return output;
}

int ParticleSystem::getTimeToLive()
{
    return TimeToLive;
}

int ParticleSystem::getTimeToLiveVar()
{
    return TimeToLiveVar;
}

int ParticleSystem::getAmount()
{
    return Amount;
}

short int ParticleSystem::getSrcBlending()
{
    // Traduccion a identificadores de la interfaz
    if (SrcBlending== GL_ZERO) return 0;
    if (SrcBlending== GL_ONE) return 1;
    if (SrcBlending== GL_DST_COLOR) return 2;
    if (SrcBlending== GL_SRC_COLOR) return 3;
    if (SrcBlending== GL_ONE_MINUS_DST_COLOR) return 4;
    if (SrcBlending== GL_ONE_MINUS_SRC_COLOR) return 5;
    if (SrcBlending== GL_SRC_ALPHA) return 6;
    if (SrcBlending== GL_ONE_MINUS_SRC_ALPHA) return 7;
    else return 99;
}

short int ParticleSystem::getDstBlending()
{
    // Traduccion a identificadores de la interfaz
    if (DstBlending== GL_ZERO) return 0;
    if (DstBlending== GL_ONE) return 1;
    if (DstBlending== GL_DST_COLOR) return 2;
    if (DstBlending== GL_SRC_COLOR) return 3;
    if (DstBlending== GL_ONE_MINUS_DST_COLOR) return 4;
    if (DstBlending== GL_ONE_MINUS_SRC_COLOR) return 5;
    if (DstBlending== GL_SRC_ALPHA) return 6;
    if (DstBlending== GL_ONE_MINUS_SRC_ALPHA) return 7;
    else return 99;
}

short int ParticleSystem::getAlphaFunction()
{
    // Traduccion a identificadores de la interfaz
    if (AlphaFunction== GL_NEVER) return 0;
    if (AlphaFunction== GL_LESS) return 1;
    if (AlphaFunction== GL_EQUAL) return 2;
    if (AlphaFunction== GL_LEQUAL) return 3;
    if (AlphaFunction== GL_GREATER) return 4;
    if (AlphaFunction== GL_NOTEQUAL) return 5;
    if (AlphaFunction== GL_GEQUAL) return 6;
    if (AlphaFunction== GL_ALWAYS) return 7;
}

float ParticleSystem::getAlphaReference()
{
    return AlphaReference;
}
